#include "misc.h"
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "log.h"


int ilog2(size_t v) {
    int l = 1;
    while ((1UL << l) < v)
        l++;
    return (1UL << l) == v ? l : l - 1;
}

const char *sockaddr_ntop(const void *addr, char *buf, size_t buflen) {
    const char *ret;
    size_t off;
    uint16_t port;

    switch (((struct sockaddr *)addr)->sa_family) {

    case AF_INET:
        ret = inet_ntop(AF_INET, &((struct sockaddr_in *)addr)->sin_addr, buf, buflen);
        if (ret) {
            off = strlen(ret);
            port = ntohs(((struct sockaddr_in *)addr)->sin_port);
        }
        break;

    case AF_INET6:
        buf[0] = '[';
        ret = inet_ntop(AF_INET6, &((struct sockaddr_in6 *)addr)->sin6_addr, buf + 1, buflen - 1);
        if (ret) {
            if ((off = strlen(ret) + 1) < buflen - 1)
                buf[off++] = ']';
            port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port);
        }
        break;

    default:
        snprintf(buf, buflen, "unknown address");
        return buf;
    }

    if (ret) {
        snprintf(buf + off, buflen - off, ":%hu", port);
    } else {
        LOGD("inet_ntop error: %s", strerror(errno));
        return NULL;
    }

    return buf;
}

int create_listen_socket(const char *host, const char *srv, int family, int sockType) {
    int sockfd = -1, haveBindErr = 0;

    int err;
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;

    memset(&hint, 0, sizeof(hint));
    hint.ai_flags = AI_PASSIVE;
    hint.ai_family = family;
    hint.ai_socktype = sockType;

    if ((err = getaddrinfo(host, srv, &hint, &ailist)) < 0) {
        LOGE("%s: Getaddrinfo(%s, %s,&hint, &ailist) error: %s",
                __FUNCTION__, host, srv, gai_strerror(err));
        return -1;
    }

    for (aip = ailist; aip; aip = ailist->ai_next) {
        if ((sockfd = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol)) < 0) {
            if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT || errno == EPROTOTYPE)
                continue;
            LOGE("Cannot create socket for %s,%s : %s",
                    host, srv, strerror(errno));
            break;
        }

        int reuse = 1;
        setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

        if (bind(sockfd, aip->ai_addr, aip->ai_addrlen) == 0)
            break;
        haveBindErr = errno;

        close(sockfd);
        sockfd = -1;
    }

    if (sockfd < 0) {
        LOGE("%s %s,%s failed: %s", haveBindErr ? "Bind to" : "Work on",
                host, srv, haveBindErr ? strerror(errno) : "No host found");
        goto FREEADDRINFO;
    }

    if (listen(sockfd, 0) < 0) {
        char addrstr[ADDRSTRLEN];
        sockaddr_ntop(aip, addrstr, sizeof(addrstr));
        LOGE("Listen on %s error: %s", addrstr, strerror(errno));
        close(sockfd);
        sockfd = -1;
        goto FREEADDRINFO;
    }

    char addrbuf[ADDRSTRLEN];
    sockaddr_ntop(aip->ai_addr, addrbuf, sizeof(addrbuf));
    LOGI("Listen on %s", addrbuf);

FREEADDRINFO:
    freeaddrinfo(ailist);

    return sockfd;
}

int get_socket_errno(int sock) {
    int error;
    socklen_t len;

    len = sizeof(error);
    if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
        LOGW("[%d] Can not get socket SO_ERROR at SOL_SOCKET level: %s", sock,
                strerror(errno));
        return 0;
    }

    return error;
}
